/**
 *
 * @file sockpool.c
 *
 * @brief manage a set of sockets 
 *
 * $Id: $
 */

/*
 * This file is part of Tsar.
 *
 * Tsar is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tsar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tsar.  If not, see <http://www.gnu.org/licenses/>.
 */


#include <sockpool.h>


/****************************************************************************
 *                                                                          *
 *                        --) SOCKPOOL (--                                  *
 *                                                                          *
 *    Sockpool is an events manager to watch several sockets. It manages a  *
 * list of events, and each events has a socket, a type and a callback.     *
 * If select(2) observes some activities of the type contained in the event *
 * then the callback is executed.                                           *
 *    Currently, only reading is implemented, so you can only set callbacks *
 * for incoming datas/connections on the socket.                            *
 *                                                                          *
 *    Sockpool uses non blocking sockets as it is based on select(2).       *
 * Non blocking sockets are possible too but the Debuan GNU/Linux man page  *
 * of select warns for this. On GNU/Linux, some socket descriptors can be   *
 * marked as ready for reading but block on recv (eg; if datas arrived but  *
 * are marked as invalid by the kernel).                                    *
 *                                                                          *
 * Using sockpool with libpcap require you to set unblocking mode on your   *
 * pcap socket descriptor. If not, you might face some portability problems *
 * related to the way pcap deal with timeouts...                            *
 *                                                                          *
 *                                                                          *
 *                                                                          *
 *                                                                          *
 ****************************************************************************/



sockpool_t * sockpool_init( void )
{
    sockpool_t * evts = NULL;

    print( MESSAGE_NOTICE, "Starting sockpool events manager\n" );
    
    /* Create a new sockpool handle to send to higher levels */
    evts = ( sockpool_t * )Malloc( sizeof( sockpool_t ) );

    /* Clear the fd set */
    FD_ZERO( &evts->masterSet );
    evts->events = NULL;
    evts->maxFd  = -1; /* any invalid one... */

    return evts;
}

int sockpool_add_event( sockpool_t * handle, int eventType, void (*callback) ( int, void * ), int sock, void * datas )
{
    event_t * evt, * addedEvent;

    evt = handle->events;
    /* evt can be NULL on the first call */
    while ( ( evt != NULL ) && ( evt->next != NULL ) )
        evt = evt->next;

    addedEvent = (event_t *)Malloc( sizeof( event_t ) );

    addedEvent->id       = GET_NEXT_EVENT_ID( evt );    /* Every event has an uniq Id */
    addedEvent->ev_type  = eventType;                   /* Event type (read/write/error) */
    addedEvent->callback = callback;                    /* Callback to process when event occurs */
    addedEvent->sock     = sock;                        /* The socket select() will watch */
    addedEvent->params   = datas;                       /* A pointer that will be passed to the callback */
    addedEvent->next     = NULL;                        /* Next event in the list */

    if ( evt == NULL )  /* In case this is the first event registered */
    {
        /* Add the event at the head of the list */
        handle->events = addedEvent;
    }
    else
    {
        /* Add the event at the end of the list */
        evt->next = addedEvent;
    }

    FD_SET( sock, &handle->masterSet );
    UPDATE_MAXFD( sock, handle->maxFd );

    debug( "Added event with id=%d for socket #%d\n", addedEvent->id, addedEvent->sock );

    return addedEvent->id;
}

int sockpool_delete_event( sockpool_t * handle, int sock )
{
    int retVal = 1;
    event_t * evt, * tmp;

    if ( handle->events == NULL )
        fatal( "Cannot delete event from an empty list!\n" );

    evt = handle->events;
    /* First item */
    if ( evt->sock == sock )
    {
        /* Remove the socket from the fd set */
        FD_CLR( evt->sock, &handle->masterSet );
        /* and update the max fd # if necessary */
        if ( evt->sock == handle->maxFd )
            update_highest_fd( handle );
        /* free the memory allocated for the event and link 
         * next events in the list */
        tmp = evt->next;
        free( evt );
        evt = NULL;
        handle->events = tmp;
        retVal = 0;
    }
    else
    {
        /* For an item in the list */
        while( evt->next != NULL )
        {
            if ( evt->next->sock == sock )
            {
                /* 
                 * More or less the same thing than above. But this time we 
                 * delete next event if it is the good one. This is required as our events list
                 * is a single chained one, we cannot work on previous element to recreate links
                 * between items when delleting one
                 */
                FD_CLR( evt->next->sock, &handle->masterSet );
                if ( evt->next->sock == handle->maxFd )
                    update_highest_fd( handle );

                tmp = evt->next->next;
                free( evt->next );
                evt->next = tmp;
 
                retVal = 0;
                break;
            }
            evt = evt->next;
        }
    }
    if ( retVal )
    {
        print( MESSAGE_WARNING, "Failed attempt to delete non-existent event (socket = %u)\n", sock );
    }
    else
    {
        debug( "Event for socket #%u deleted!\n", sock );
    }

    return retVal;
}

/* Select needs the biggest value of file descriptor contained in the fd set it watch */
int update_highest_fd( sockpool_t * handle )
{
    event_t * evt;

    for ( evt = handle->events; evt != NULL; evt = evt->next )
    {
        if ( evt->sock > handle->maxFd )
            handle->maxFd = evt->sock;
    }
    return 0;
}

int sockpool_shutdown( sockpool_t * handle )
{
    int retVal;
    event_t * evt;

    /* Catch higher levels errors */
    if ( handle == NULL )
    {
        retVal = 1;
    }
    else
    {
        /* free() every item in the list */
        for ( evt = handle->events; evt != NULL; evt = evt->next )
        {
            /* we transmit evt->sock as selection of item to delete is made according to socket fileno */
            sockpool_delete_event( handle, evt->sock );
        }
        /* then free() the handle */
        free( handle );
        retVal = 0;
    }

    return retVal;
}

int sockpool_manager( fd_set * fds, sockpool_t * handle )
{
    int n, nfds, retVal, i, s;
    struct timeval timeout = { 
        SELECT_TIMEOUT_SEC,
        SELECT_TIMEOUT_MS
    };

    nfds = handle->maxFd;
    n = select( nfds + 1, fds, NULL, NULL, &timeout );
    switch( n )
    {
        /* Error case */
        case    -1  :
            Perror( "Select" );
            retVal = SOCKPOOL_ERROR;
            break;

        /* Nothing available */
        case    0   :
            retVal = TIMEOUT_REACHED;
            break;

        /* Activity detected */
        default :
            s = n;
            for ( i = 0; ( n >= 0 ) && ( i <= nfds ); i++ )
            {
                if ( FD_ISSET( i, fds ) )
                {
                    process_registered_action( handle, i );
                    n -= 1;    /* One less descriptor to check for in this loop  */
                }
            }
            retVal = EVENT_PROCESSED;
            break;
    }

    return retVal;
}

int sockpool_loop( sockpool_t * handle )
{
    int retVal = 0, err = 0;
    fd_set workingSet;

    while( Loop )
    {

        /*debug( "Entering sockpool's main loop (err = %d)\n", err );*/
        /* This has to be cleared in each loop, because sockpool_manager modifies its arguments */
        FD_ZERO( &workingSet );
        memcpy( &workingSet, &handle->masterSet, sizeof( workingSet ) );
        if ( ( err = sockpool_manager( &workingSet, handle ) ) == SOCKPOOL_ERROR )
        {
            if ( Loop == false )
            {
                retVal = 0;     /* We must have caught a SIGINT */
            }
            else /* But in this case I'm affraid we've got a real error... */
            {
                print_error( "Sockpool failure\n" );
                Loop = false;   /* Turns the things off */
                retVal = 1;
            }
        }
    }

    return retVal;
}

void process_registered_action( sockpool_t * handle, int sock )
{
    event_t * evt;

    /*debug( "%s looking for a callback for activity on fd #%d\n", __FUNCTION__, sock );*/
    for ( evt = handle->events; evt != NULL; evt = evt->next )
    {
        if ( evt->sock == sock )
        {
            /* Execute the callback for activity on this socket */
            evt->callback( evt->sock, evt->params );
            return;
        }
    }
}

